package com.my.study.reflection.utils;

import com.my.study.reflection.VFS;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.ResolverUtil;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 解析类
 *
 * @author: yidujun
 * @create: 2021/07/16 09:41
 */
@Slf4j
public class Resolver<T> {

    /**
     * 匹配成功的类
     */
    private Set<Class<? extends T>> matches = new HashSet<>();

    private ClassLoader classLoader;

    public interface Test {
        /**
         * 匹配
         *
         * @param type
         * @return
         */
        boolean matches(Class<?> type);
    }

    public static class IsA implements Test {

        private Class<?> parent;

        public IsA(Class<?> parentType) {
            this.parent = parentType;
        }

        @Override
        public boolean matches(Class<?> type) {
            return null != type && parent.isAssignableFrom(type);
        }

        @Override
        public String toString() {
            return "is assignable to " + parent.getSimpleName();
        }
    }

    public static class AnnotatedWith implements Test {
        private Class<? extends Annotation> annotation;

        public AnnotatedWith(Class<? extends Annotation> annotation) {
            this.annotation = annotation;
        }

        @Override
        public boolean matches(Class<?> type) {
            return null != type && type.isAnnotationPresent(annotation);
        }

        @Override
        public String toString() {
            return "annotated with @" + annotation.getSimpleName();
        }
    }

    public Set<Class<? extends T>> getClasses() {
        return matches;
    }

    public ClassLoader getClassLoader() {
        return classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * 从指定的包中尝试获取{@code parent}的子类或实现类。
     * <p>
     *    如果{@code parent}是普通类，获取其子类；如果{@code parent}是接口，获取其实现类
     * </p>
     *
     * @param parent 超类
     * @param packageNames 指定的包名，权限定名
     * @return Resolver
     */
    public Resolver<T> findImplementations(Class<?> parent, String... packageNames) {
        if (null == packageNames) {
            return this;
        }
        Test test = new IsA(parent);
        for (String packageName : packageNames) {
            find(test, packageName);
        }
        return this;
    }

    /**
     * 从指定的包中获取注解了注解{@code annotation}的类
     *
     * @param annotation 注解
     * @param packageNames 指定的包名，权限定名
     * @return Resolver
     */
    public Resolver<T> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
        if (null == packageNames) {
            return this;
        }

        Test test = new AnnotatedWith(annotation);
        for (String pkg : packageNames) {
            find(test, pkg);
        }

        return this;
    }

    /**
     * 扫描包以及子包，断言 Test 如果返回 true，则把对应的类对象加入到{@link Resolver#matches}中
     *
     * @param test 断言
     * @param packageName 指定的包名，权限定名
     * @return Resolver
     */
    public Resolver<T> find(Test test, String packageName) {
        String packagePath = getPackagePath(packageName);

        try {
            List<String> children = VFS.getInstance().list(packagePath);
            for (String child : children) {
                if (child.endsWith(".class")) {
                    addIfMatching(test, child);
                }
            }
        } catch (Exception ioe) {
            log.error("Could not read package: " + packageName, ioe);
        }
        return this;
    }

    protected String getPackagePath(String packageName) {
        return packageName == null ? null : packageName.replace('.', '/');
    }

    @SuppressWarnings("unchecked")
    protected void addIfMatching(Test test, String fqn) {
        try {
            String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
            ClassLoader loader = getClassLoader();
            if (log.isDebugEnabled()) {
                log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
            }

            Class<?> type = loader.loadClass(externalName);
            if (test.matches(type)) {
                matches.add((Class<T>) type);
            }
        } catch (Throwable t) {
            log.warn("Could not examine class '" + fqn + "'" + " due to a " +
                    t.getClass().getName() + " with message: " + t.getMessage());
        }
    }
}
